﻿// *********************************************************
// 
//     Copyright (c) Microsoft. All rights reserved.
//     This code is licensed under the Apache License, Version 2.0.
//     THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//     ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//     IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//     PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
// 
// *********************************************************
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Bio.Algorithms.Alignment;
using System.Globalization;
using Bio;
using RepeatResolutionUtil.Properties;
using Bio.IO.FastA;

namespace RepeatResolutionUtil.IO
{
     /// <summary>
    /// This parser reads from a source of text that contains DeltaAlignments
    /// and converts the data to in-memory DeltaAlignment objects.  
    /// </summary>
    public sealed class DeltaAlignmentParser : IDisposable
    {
        #region Member variables
        /// <summary>
        /// String to hold the current line in file
        /// </summary>
        private string line = string.Empty;

        /// <summary>
        /// String to hold the last read query sequence id
        /// </summary>
        private string lastReadQuerySequenceId = string.Empty;

        /// <summary>
        /// Sequence to hold the last sequence
        /// </summary>
        private Sequence sequence = null;

        /// <summary>
        /// Query sequence enumerator
        /// </summary>
        private IEnumerable<ISequence> querySequences = null;
        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the DeltaAlignmentParser class by 
        /// loading the specified filename.
        /// </summary>
        /// <param name="deltaFilename">Name of the File.</param>
        public DeltaAlignmentParser(string deltaFilename, FastAParser queryParser)
        {
            this.Open(deltaFilename, queryParser);
        }
        #endregion

        #region Properties

        /// <summary>
        /// Gets the delta filename.
        /// </summary>
        public string DeltaFilename { get; private set; }


        /// <summary>
        /// Gets the query parser.
        /// </summary>
        public FastAParser QueryParser { get; private set; }

        #endregion

        #region Methods
        /// <summary>
        /// Opens the stream for the specified file.
        /// </summary>
        /// <param name="deltaFilename">Name of the file to open.</param>
        /// <param name="queryParser">Parser to parse the query file.</param>
        public void Open(string deltaFilename, FastAParser queryParser)
        {
            // if the file is already open throw invalid 
            if (!string.IsNullOrEmpty(this.DeltaFilename))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.FileAlreadyOpen, this.DeltaFilename));
            }

            // Validate the file - by try to open.
            using (new StreamReader(deltaFilename))
            {
            }

            this.DeltaFilename = deltaFilename;
            if (queryParser == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.INVALID_INPUT_FILE, "Query file"));
            }
            this.QueryParser = queryParser;
            this.querySequences = queryParser.Parse();
        }

        /// <summary>
        /// Returns an IEnumerable of DeltaAlignment in the file being parsed.
        /// </summary>
        /// <returns>Returns DeltaAlignment collection.</returns>
        
        public IEnumerable<IEnumerable<DeltaAlignment>> Parse()
        {
            bool skipBlankLine = true;
            IList<DeltaAlignment> deltaAlignments = new List<DeltaAlignment>();
            string message = string.Empty;
            IEnumerator<ISequence> queryEnumerator = this.querySequences.GetEnumerator();

            using (StreamReader streamReader = new StreamReader(this.DeltaFilename))
            {
                if (streamReader.EndOfStream)
                {
                    message = string.Format(
                                CultureInfo.InvariantCulture,
                                Resources.INVALID_INPUT_FILE,
                                this.DeltaFilename);

                    throw new FileFormatException(message);
                }

                ReadNextLine(streamReader);
                do
                {
                    if (line == null || !line.StartsWith(">", StringComparison.OrdinalIgnoreCase))
                    {
                        message = string.Format(
                                CultureInfo.InvariantCulture,
                                Resources.INVALID_INPUT_FILE,
                                this.DeltaFilename);

                        throw new FileFormatException(message);
                    }

                    DeltaAlignment deltaAlignment = null;
                    
                    //First line - reference id
                    string referenceId = line.Substring(1);

                    // Read next line.
                    ReadNextLine(streamReader);

                    //Second line - Query sequence id
                    string queryId = line;

                    //fetch the query sequence from the query file
                    if (!string.IsNullOrEmpty(queryId))
                    {
                        bool sequenceFound = true;
                        int resetCount = 0;
                        if (queryId != lastReadQuerySequenceId)
                        {
                            sequenceFound = false;
                            while (!sequenceFound && resetCount < 2)
                            {
                                //locate the sequence in the query file
                                while (queryEnumerator.MoveNext())
                                {
                                    sequence = (Sequence)queryEnumerator.Current;
                                    if (sequence != null && sequence.ID == queryId)
                                    {
                                        sequenceFound = true;
                                        lastReadQuerySequenceId = queryId;
                                        break;
                                    }
                                }
                                if (!sequenceFound)
                                {
                                    queryEnumerator = this.querySequences.GetEnumerator();
                                    resetCount++;
                                }
                            }
                        }

                        if (!sequenceFound || sequence == null)
                        {
                            message = string.Format(
                                   CultureInfo.InvariantCulture,
                                   Resources.INVALID_QUERY_FILE,
                                   queryId,
                                   this.QueryParser.Filename);

                            throw new FileFormatException(message);
                        }

                        Sequence refEmpty = new Sequence(sequence.Alphabet, "A", false);
                        refEmpty.ID = referenceId;

                        deltaAlignment = new DeltaAlignment(refEmpty, sequence);
                    }

                    //Fourth line - properties of deltaalignment
                    // Read next line.
                    ReadNextLine(streamReader);

                    string[] deltaAlignmentProperties = line.Split(' ');
                    if (deltaAlignmentProperties != null && deltaAlignmentProperties.Length == 7)
                    {
                        long temp;
                        deltaAlignment.FirstSequenceStart = long.TryParse(deltaAlignmentProperties[0], out temp) ? temp : 0;
                        deltaAlignment.FirstSequenceEnd = long.TryParse(deltaAlignmentProperties[1], out temp) ? temp : 0;
                        deltaAlignment.SecondSequenceStart = long.TryParse(deltaAlignmentProperties[2], out temp) ? temp : 0;
                        deltaAlignment.SecondSequenceEnd = long.TryParse(deltaAlignmentProperties[3], out temp) ? temp : 0;
                        int error;
                        deltaAlignment.Errors = int.TryParse(deltaAlignmentProperties[4], out error) ? error : 0;
                        deltaAlignment.SimilarityErrors = int.TryParse(deltaAlignmentProperties[5], out error) ? error : 0;
                        deltaAlignment.NonAlphas = int.TryParse(deltaAlignmentProperties[6], out error) ? error : 0;
                    }

                    //Fifth line - either a 0 - marks the end of the delta alignment or they are deltas
                    while (line != null && !line.StartsWith("*", StringComparison.OrdinalIgnoreCase))
                    {
                        long temp;
                        if (long.TryParse(line, out temp))
                        {
                            deltaAlignment.Deltas.Add(temp);
                        }
                        // Read next line.
                        line = streamReader.ReadLine();

                        // Continue reading if blank line found.
                        while (skipBlankLine && line != null && string.IsNullOrEmpty(line))
                        {
                            line = streamReader.ReadLine();
                        }
                    }

                    deltaAlignments.Add(deltaAlignment);
                    //Read the next line
                    line = streamReader.ReadLine();
                    if (line != null)
                    {
                        if (line.StartsWith("--", StringComparison.OrdinalIgnoreCase))
                        {
                            yield return deltaAlignments;
                            //clear the inner list
                            deltaAlignments = new List<DeltaAlignment>();

                            //skip until the next valid delta is found
                            do
                            {
                                line = streamReader.ReadLine();
                            }
                            while (line != null && line.StartsWith("--", StringComparison.OrdinalIgnoreCase));
                        }
                    }
                }
                while (line != null);
            }
            yield return deltaAlignments;
        }

        /// <summary>
        /// Closes streams used.
        /// </summary>
        public void Close()
        {
            this.DeltaFilename = null;
        }

        /// <summary>
        /// Disposes the underlying stream.
        /// </summary>
        public void Dispose()
        {
            this.Close();
            GC.SuppressFinalize(this);
        }

        #region Private Methods

        /// <summary>
        /// Reads the next line skipping the blank lines
        /// </summary>
        /// <param name="streamReader"></param>
        private void ReadNextLine(StreamReader streamReader)
        {
            // Read next line.
            line = streamReader.ReadLine();
            string message = string.Empty;

            // Continue reading if blank line found.
            while (line != null && string.IsNullOrEmpty(line))
            {
                line = streamReader.ReadLine();
            }

            if (line == null)
            {
                message = string.Format(
                    CultureInfo.InvariantCulture,
                    Resources.InvalidSymbolInString,
                    string.Empty);
                throw new FileFormatException(message);
            }
        }
        #endregion
        #endregion
    }
}
